#ifndef ASPECT_H
#define ASPECT_H

#include <utility>

#define HAS_MEMBER(member)                                                     \
    template <typename T, typename... Args>                                    \
    struct has_member_##member                                                 \
    {                                                                          \
    private:                                                                   \
        template <typename U>                                                  \
        static auto check(int)                                                 \
            -> decltype(std::declval<U>().member(std::declval<Args>()...),     \
                        std::true_type());                                     \
        template <typename U>                                                  \
        static std::false_type check(...);                                     \
                                                                               \
    public:                                                                    \
        enum                                                                   \
        {                                                                      \
            value = std::is_same<decltype(check<T>(0)), std::true_type>::value \
        };                                                                     \
    };

HAS_MEMBER(Before)
HAS_MEMBER(After)
HAS_MEMBER(SetType)

template <typename Func, typename... Args>
struct Aspect
{
    explicit Aspect(Func&& f) : m_func(std::forward<Func>(f)) {}

    explicit Aspect(Func&& f, std::string clsName, std::string funcName)
        : m_className(clsName),
          m_funcName(funcName),
          m_func(std::forward<Func>(f))
    {
    }

    template <typename T>
    typename std::enable_if<has_member_SetType<T, std::string>::value>::type
    SetTypeName(T&& aspect)
    {
        if (!m_className.empty() && !m_funcName.empty()) {
            aspect.SetType(m_className + "::" + m_funcName);
        }
    }

    template <typename T>
    typename std::enable_if<!has_member_SetType<T, std::string>::value>::type
    SetTypeName(T&&)
    {
    }

    template <typename T>
    typename std::enable_if<has_member_Before<T>::value &&
                            has_member_After<T>::value>::type
    invoke(Args&&... args, T&& aspect)
    {
        SetTypeName(std::forward<T>(aspect));
        aspect.Before();
        m_func(std::forward<Args>(args)...);
        aspect.After();
    }

    template <typename T>
    typename std::enable_if<has_member_Before<T>::value &&
                            !has_member_After<T>::value>::type
    invoke(Args&&... args, T&& aspect)
    {
        SetTypeName(std::forward<T>(aspect));
        aspect.Before();
        m_func(std::forward<Args>(args)...);
    }

    template <typename T>
    typename std::enable_if<!has_member_Before<T>::value &&
                            has_member_After<T>::value>::type
    invoke(Args&&... args, T&& aspect)
    {
        SetTypeName(std::forward<T>(aspect));
        m_func(std::forward<Args>(args)...);
        aspect.After();
    }

    template <typename Head, typename... Tail>
    typename std::enable_if<has_member_Before<Head>::value &&
                            has_member_After<Head>::value>::type
    invoke(Args&&... args, Head&& headAspect, Tail&&... tailAspect)
    {
        SetTypeName(std::forward<Head>(headAspect));
        headAspect.Before();
        invoke(std::forward<Args>(args)..., std::forward<Tail>(tailAspect)...);
        headAspect.After();
    }

    template <typename Head, typename... Tail>
    typename std::enable_if<!has_member_Before<Head>::value &&
                            has_member_After<Head>::value>::type
    invoke(Args&&... args, Head&& headAspect, Tail&&... tailAspect)
    {
        SetTypeName(std::forward<Head>(headAspect));
        invoke(std::forward<Args>(args)..., std::forward<Tail>(tailAspect)...);
        headAspect.After();
    }

    template <typename Head, typename... Tail>
    typename std::enable_if<has_member_Before<Head>::value &&
                            !has_member_After<Head>::value>::type
    invoke(Args&&... args, Head&& headAspect, Tail&&... tailAspect)
    {
        SetTypeName(std::forward<Head>(headAspect));
        headAspect.Before();
        invoke(std::forward<Args>(args)..., std::forward<Tail>(tailAspect)...);
    }

private:
    Func m_func;
    std::string m_className;
    std::string m_funcName;
};

template <typename... Ap, typename... Args, typename Func>
void invoke(Func&& f, Args&&... args)
{
    Aspect<Func, Args...> asp(std::forward<Func>(f));
    asp.template invoke(std::forward<Args>(args)..., Ap()...);
}

template <typename... Ap, class U, class V, typename... Args, typename Func>
void invoke(Func&& f, U& clsName, V& funcName, Args&&... args)
{
    Aspect<Func, Args...> asp(std::forward<Func>(f), clsName, funcName);
    asp.template invoke(std::forward<Args>(args)..., Ap()...);
}

#endif // ASPECT_H
